library MoveSpeedXGUI requires AutoIndex
/* 
* v1.1.0.0
*************************************************************************************
*   
*   This library allows you to set unit movement speeds beyond 522 without bugs.
*   This is an extension of the library MoveSpeedX, but is formatted for GUI use.
*   Credits to Jesus4Lyf for the original system.
*
************************************************************************************
*
*   SETTINGS
*/
globals
    private constant real PERIOD = 0.03125
        //  This is the period on which all units will be run.
        // If you lower this value, movement bonuses will be smoother,
        // but will require more processing power (lag more).
        //  Also, the lower this is, the higher the move speed can be
        // before it starts bugging on waypoints. The lowest valid
        // period is 0.00125. A period of 0.00625 is very robust.
    private constant real MARGIN = 0.01
        // This is the margin of approximation when comparing reals.
        // You will most likely not need to change this.
endglobals
/*
************************************************************************************
*
*    Functions
*
*        function GetUnitMoveSpeedX takes unit whichUnit returns real
*           - Returns a unit movement speed. The GUI function will
*           - not return the correct value. This function will always
*           - return the correct value regardless of whether the unit
*           - has a movement speed beyond 522.
*
************************************************************************************
*
*   REQUIREMENTS
*
*       1.  JassNewGen Pack v5d
*       2.  JassHelper 0.A.2.B
*       3.  Any unit indexer
*
*   HOW TO IMPLEMENT
*   
*       1.  Copy the 'folder' MoveSpeedX.
*       2.  Paste it into your map.
*       3.  Open "Advanced -> Gameplay Constants".
*       4.  Checkmark "Use Custom Gameplay Constants".
*       5.  Find the field, "Movement - Unit Speed - Maximum", change
*           that to 522.
*       6.  Find the field, "Movement - Unit Speed - Minimum", hold
*           shift and click, and change it to 0.
*       7.  Read HOW TO USE. 
*
************************************************************************************
*
*   HOW TO USE
*
*       This system will automatically work by itself. You can use the
*       normal GUI function for modifying unit movement speeds. Simply
*       use "Unit - Set Movement Speed", input whatever value you want,
*       and you are good to go! It will handle values beyond 522 by itself.
*
*       HOWEVER, the GUI function will not return correct values if a unit
*       has a movement speed greater than 522. To fix this, use the function
*       GetUnitMoveSpeedX to return the correct value. A sample is given in
*       the trigger "Speed Change" in the test map.
*
************************************************************************************
*
*   NOTES
*
*       Units that were issued orders as groups might not *always* end up in the proper
*       "order". (they might not end up in an organized formation) They do sometimes though.
*       This is only for units with speeds above 522.
*
*       This also will not factor in bonuses and probably not slows either.
*
*       Units may waddle around the point for a little bit. Reduce PERIOD to fix
*       it a little bit. I recommend about 0.02 if you have really high speeds.
*
************************************************************************************/

    private function ApproxEqual takes real A, real B returns boolean
        return (A >= (B - MARGIN)) and (A <= (B + MARGIN))
    endfunction
    
    private module M
        private static trigger issued = CreateTrigger()
        
        thistype next 
        thistype prev
        
        boolean enabled
        unit curr
        real currSpeed
        real speed
        real x
        real y
        real ox
        real oy

        static method get takes unit whichUnit returns thistype
            return thistype(GetUnitId(whichUnit))
        endmethod
        
        method destroy takes nothing returns nothing
            if not this.enabled then
                return
            endif
            set this.next.prev = this.prev
            set this.prev.next = this.next
            set this.currSpeed = 0
            set this.enabled = false
        endmethod
            
        private static method periodic takes nothing returns nothing
            local thistype this = thistype(0).next // first instance in list
            local real nx // the x-coordinate after tick
            local real ny // the y-coordinate after tick
            local real dx // distance between new-x and old-x
            local real dy // distance between new-y and old-y
            local real d  // distance between new point and old point
            local integer orderId // the unit's current order
            local unit u // unit being affected  

            loop
                exitwhen this == 0
                set u  = .curr
                set nx = GetUnitX(u)
                set ny = GetUnitY(u)
                if IsUnitType(u, UNIT_TYPE_DEAD) then
                    call this.destroy()
                elseif not ApproxEqual(nx, .x) or not ApproxEqual(ny, .y) then
                    if (not IsUnitPaused(u)) and GetUnitAbilityLevel(u, 'BSTN') == 0 and GetUnitAbilityLevel(u, 'BPSE') == 0 then
                        set orderId = GetUnitCurrentOrder(u)
                        set dx = nx - .x
                        set dy = ny - .y
                        set d  = SquareRoot(dx * dx + dy * dy)
                        set dx = dx / d * .speed // move the unit offset-x by this
                        set dy = dy / d * .speed // move the unit offset-y by this
                        
                        if (orderId == 851986 or orderId == 851971) and /*
                        */ (this.ox - nx)*(this.ox - nx) < (dx*dx) and /*
                        */ (this.oy - ny)*(this.oy - ny) < (dy*dy) then
                            // if the unit is issued a move or smart order and they are near their destination
                            // then move them there instantly (removes a bit of glitchyness towards the end)
                            call SetUnitX(u, .ox) 
                            call SetUnitY(u, .oy)
                            set .x = .ox
                            set .y = .oy 
                            call IssueImmediateOrderById(u, 851972) // order them to stop
                        else
                            set .x = nx + dx
                            set .y = ny + dy
                            call SetUnitX(u, .x)
                            call SetUnitY(u, .y)
                        endif
                    endif
                endif
                set this = this.next
            endloop
            set u = null
        endmethod
        
        static method create takes unit whichUnit, real newSpeed returns thistype
            local thistype this = get(whichUnit)

            debug call BJDebugMsg("MoveSpeedStruct create" + I2S(this))

            set this.next = thistype(0).next
            set thistype(0).next.prev = this
            set thistype(0).next = this
            set this.prev  = 0
            set this.curr  = whichUnit
            set this.speed = (newSpeed - 522) * PERIOD
            set this.x     = GetUnitX(whichUnit)
            set this.y     = GetUnitY(whichUnit)
            set this.enabled = true
            return this
        endmethod
        
        static method update takes unit whichUnit, real newSpeed returns nothing
            local thistype this = get(whichUnit)
            if this.enabled then
                if newSpeed > 522 then
                    set this.speed = (newSpeed - 522) * PERIOD
                else
                    call this.destroy()
                endif
            elseif newSpeed > 522 then
                call thistype.create(whichUnit, newSpeed)
            endif
            if newSpeed >= 0 then
                set this.currSpeed = newSpeed
                if newSpeed < 522 then
                    call SetUnitMoveSpeed(whichUnit, newSpeed)
                endif
            endif
        endmethod
        
        private static method storeOrderPoint takes nothing returns boolean
            local thistype this = get(GetTriggerUnit())
            set this.ox = GetOrderPointX()
            set this.oy = GetOrderPointY()
            return false
        endmethod
    
        private static method onInit takes nothing returns nothing
            call TimerStart(CreateTimer(), PERIOD, true, function thistype.periodic)
            call TriggerRegisterAnyUnitEventBJ(issued, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
            call TriggerAddCondition(issued, Condition(function thistype.storeOrderPoint))
        endmethod
    endmodule
    
    private struct MoveSpeedStruct extends array
        implement M
    endstruct

    function DestroyMoveSpeedStruct takes unit whichUnit returns nothing
        call MoveSpeedStruct.get(whichUnit).destroy()
    endfunction
    
    function GetUnitMoveSpeedX takes unit whichUnit returns real
        local MoveSpeedStruct move = MoveSpeedStruct.get(whichUnit)
        if move.enabled then
            return move.currSpeed
        endif
        return GetUnitMoveSpeed(whichUnit)
    endfunction
    
    function SetUnitMoveSpeedX takes unit whichUnit, real newSpeed returns nothing
        call MoveSpeedStruct.update(whichUnit, newSpeed)
    endfunction

    //hook SetUnitMoveSpeed SetUnitMoveSpeedX
endlibrary